Vue 3.3 defineProps 的类型声明新特性
Vue 3.3 对 <script setup> 中的 defineProps 宏进行了重大改进,支持直接使用 TypeScript 的 interface 或类型别名作为泛型参数,无需再使用传统的运行时声明方式。
传统写法的问题
在 Vue 3.3 之前,如果要定义复杂类型的 Props,需要使用 PropType 进行类型断言:
// 传统写法:繁琐且可读性差
defineProps({
footerInfo: {
type: Object as PropType<{
icp: string
copyright: string
links: { href: string; title: string }[]
contacts: { email: string; phone?: string; address?: string }
}>,
required: true,
},
})
typescript
这种写法存在明显缺陷:
- 需要额外引入
PropType - 类型断言语法冗长
- 默认值设置不够直观
- 编辑器类型提示有限
Vue 3.3 新写法
1. 定义 Interface
首先定义组件所需的 TypeScript 接口:
interface LinkType {
href: string
title: string
}
interface ContactType {
email: string // 必传属性
phone?: string // 可选属性
address?: string // 可选属性
wechat?: string // 可扩展:微信链接
weibo?: string // 可扩展:微博链接
}
interface FooterItem {
icp: string
copyright: string
links: LinkType[]
contacts: ContactType
}
typescript
2. 使用类型化的 defineProps
直接将 Interface 作为泛型参数传入:
const props = defineProps<FooterItem>()
typescript
仅需一行代码即可完成 Props 的类型声明,编译器会自动将 TypeScript 类型转换为运行时的 Props 校验。
3. 使用 withDefaults 设置默认值
Vue 3.3 提供了 withDefaults 宏,用于为类型化 Props 设置默认值:
const props = withDefaults(defineProps<FooterItem>(), {
icp: '',
copyright: '© 2024',
// 数组类型必须使用箭头函数返回
links: () => [
{ href: 'https://blog.example.com', title: '博客' },
{ href: 'https://example.com', title: '官方网站' },
],
// 对象类型也必须使用箭头函数返回
contacts: () => ({
email: 'contact@example.com',
}),
})
typescript
关键规则:数组和对象类型的默认值必须使用箭头函数返回,避免多个组件实例共享同一引用。
实战:重构 Footer 组件
模板部分改造
将硬编码的数据替换为 Props 驱动的动态渲染:
<template>
<footer>
<!-- 友链信息 -->
<ul v-if="props.links && props.links.length > 0">
<li v-for="item in props.links" :key="item.href">
<a :href="item.href">{{ item.title }}</a>
</li>
</ul>
<!-- 联系信息 -->
<ul v-if="props.contacts">
<li>
<a :href="`mailto:${props.contacts.email}`">
{{ props.contacts.email }}
</a>
</li>
<li v-if="props.contacts.phone">{{ props.contacts.phone }}</li>
<li v-if="props.contacts.address">{{ props.contacts.address }}</li>
</ul>
<!-- 图片处理 -->
<img :src="props.contacts.wechat || defaultWechat" alt="微信" />
<img :src="props.contacts.weibo || defaultWeibo" alt="微博" />
</footer>
</template>
vue
图片路径处理
在 Vue 中,动态拼接的图片路径无法被构建工具正确解析。必须将图片作为模块静态导入:
// 正确做法:静态导入
import defaultWechat from '@/assets/images/wechat.jpg'
import defaultWeibo from '@/assets/images/weibo.jpg'
// 错误做法:动态拼接不会被 Vite/Webpack 处理
// <img :src="props.contacts.wechat || '@/assets/images/wechat.jpg'" />
typescript
导入后即可在模板中安全使用 || 运算符提供回退值:
<img :src="props.contacts.wechat || defaultWechat" />
vue
对比总结
| 特性 | 传统写法 | Vue 3.3 类型化写法 |
|---|---|---|
| 类型声明 | type: Object as PropType<T> | 泛型参数 defineProps<T>() |
| 默认值 | default 属性 | withDefaults 宏 |
| 代码量 | 多 | 少 |
| 类型推断 | 有限 | 完整 |
| 编辑器支持 | 一般 | 优秀 |
最佳实践
- 优先使用
defineProps<T>()-- Vue 3.3+ 项目统一使用类型化声明 - 数组/对象默认值用箭头函数 --
withDefaults中避免直接赋值引用类型 - 静态导入图片资源 -- 不依赖动态路径拼接,确保构建工具能处理
- Interface 独立管理 -- 复杂类型可抽离到
types/目录下 - 可选属性标记
?-- 明确区分必传和可选 Props
↑